package de.tudarmstadt.ukp.muip.fibqdojo;

import java.util.Collections;
import java.util.List;

import com.google.common.base.Optional;
import com.google.common.collect.Lists;

import de.tudarmstadt.ukp.muip.fibqdojo.interfaces.IFIBQ;
import de.tudarmstadt.ukp.muip.fibqdojo.interfaces.IFIBQBlankSetter;
import de.tudarmstadt.ukp.muip.fibqdojo.interfaces.IFIBQDistractor;
import de.tudarmstadt.ukp.muip.fibqdojo.interfaces.IFIBQDistractorProvider;
import de.tudarmstadt.ukp.muip.fibqdojo.interfaces.IFIBQDojo;
import de.tudarmstadt.ukp.muip.fibqdojo.interfaces.IFIBQFilter;
import de.tudarmstadt.ukp.muip.fibqdojo.interfaces.IFIBQSentence;
import de.tudarmstadt.ukp.muip.fibqdojo.visitor.AssembleQuestionVisitor;
import de.tudarmstadt.ukp.muip.fibqdojo.visitor.SentenceAnswerVisitor;

/**
 * Compiles FIB-Questions comprising of a blanked word in the sentence offering
 * n-distractors including the correct answers. The provided input sentences can
 * be filtered according to provided filters and their filter rules. Note on the
 * filters: If several filters are used of which one/some drops/excludes
 * sentences, make sure to place the dropping filter at the first index in the
 * provided array in order to reduce the remaining sentences for the subsequent
 * filters. This implementations creates a questions without distractors if no
 * could be retrieved from the provided provider. If this behaviour is undesired
 * override.
 * 
 * @author Tobias Horsmann
 */
public class FIBQDojo implements IFIBQDojo {

    private final IFIBQDistractorProvider distractorProvider;
    private final List<IFIBQSentence> sentences;
    private final IFIBQBlankSetter blankSetter;
    private final IFIBQFilter[] filters;
    private final boolean dropSentenceWithoutBlank;

    public FIBQDojo(List<IFIBQSentence> sentences, IFIBQFilter[] filters, IFIBQBlankSetter blankSetter,
            IFIBQDistractorProvider distractorProvider, boolean dropSentenceWithoutBlank) {
        this.sentences = sentences;
        this.blankSetter = blankSetter;
        this.distractorProvider = distractorProvider;
        this.filters = filters;
        this.dropSentenceWithoutBlank = dropSentenceWithoutBlank;
    }

    public List<IFIBQ> generateQuestions() {
        List<IFIBQSentence> filtered = filterSentences();
        List<IFIBQ> fibqs = assembleQuestions(filtered);
        return fibqs;
    }

    private List<IFIBQSentence> filterSentences() {
        List<IFIBQSentence> filteredSentences = Lists.newArrayList(sentences);

        for (IFIBQFilter filter : filters) {
            filteredSentences = applyFilter(filter, filteredSentences);
        }
        return filteredSentences;
    }

    private List<IFIBQSentence> applyFilter(IFIBQFilter filter, List<IFIBQSentence> filteredSentences) {
        List<IFIBQSentence> filtered = Lists.newArrayList();
        for (IFIBQSentence sentence : filteredSentences) {
            Optional<IFIBQSentence> filterResult = filter.apply(sentence);
            if (filterResult.isPresent()) {
                filtered.add(filterResult.get());
            }
        }
        return filtered;
    }

    private List<IFIBQ> assembleQuestions(List<IFIBQSentence> filtered) {
        List<IFIBQ> fibqs = Lists.newArrayList();

        for (IFIBQSentence sentence : filtered) {
            IFIBQSentence blankedSentence = blankSetter.setBlankTokens(sentence);
            if (dropSentenceWithoutBlank && !blankedSentence.isMarked()) {
                continue;
            }
            List<IFIBQDistractor> distractors = getDistractors(blankedSentence);
            // if (distractors.size() <= 1) {
            // continue;
            // }
            fibqs.add(new FIBQ(makeStringList(blankedSentence), distractors));
        }
        return fibqs;
    }

    private List<String> makeStringList(IFIBQSentence blankedSentence) {
        AssembleQuestionVisitor visitor = new AssembleQuestionVisitor();
        blankedSentence.accept(visitor);
        return visitor.getBlankedSentence();
    }

    private List<IFIBQDistractor> getDistractors(IFIBQSentence sentence) {
        List<String> stringDistractors = distractorProvider.getDistractors(sentence);

        List<IFIBQDistractor> fibqDistractors = Lists.newArrayList();
        for (String distractor : stringDistractors) {
            fibqDistractors.add(new Distractor(distractor, false));
        }

        SentenceAnswerVisitor answerVisitor = new SentenceAnswerVisitor();
        sentence.accept(answerVisitor);
        Optional<String> answer = answerVisitor.getAnswer();
        if (!answer.isPresent()) {
            return Lists.newArrayList();
        }
        fibqDistractors.add(new Distractor(answer.get(), true));

        Collections.shuffle(fibqDistractors);
        return fibqDistractors;
    }

}
